package net.centrevillage.mgtw

import java.awt.AlphaComposite
import java.awt.Canvas
import java.awt.Color
import java.awt.Cursor
import java.awt.FlowLayout
import java.awt.Font
import java.awt.FontMetrics
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.GraphicsEnvironment
import java.awt.Point
import java.awt.color.ColorSpace
import java.awt.event.KeyEvent
import java.awt.event.MouseEvent
import java.awt.geom.Ellipse2D
import java.awt.geom._
import java.awt.image._
import java.awt.PopupMenu
import java.awt.MenuItem
import java.awt.SystemTray
import java.awt.TrayIcon
import java.awt.TrayIcon.MessageType
import java.awt.event._
import java.io.IOException
import java.io.BufferedReader
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.OutputStream
import java.io.InputStreamReader
import java.io.FileReader
import java.io.FileWriter
import java.util.Timer
import java.util.TimerTask
import java.util.Date
import java.net.URL
import javax.imageio.ImageIO
import twitter4j._
import twitter4j.api._
import twitter4j.auth._
import twitter4j.conf._
import twitter4j.json._
import twitter4j.management._
import twitter4j.media._
import twitter4j.util._

import org.yaml.snakeyaml.Yaml

import scala.collection.JavaConversions._
import scala.collection.immutable.SortedMap
import scala.collection.immutable.TreeMap
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.HashSet
import scala.collection.mutable.Queue
import scala.collection.mutable.SynchronizedQueue
import scala.collection.mutable.{LinkedHashMap => LHMap}
import scala.collection.mutable.{ListBuffer => MList}
import scala.collection.mutable.{Map => MMap}
import scala.io._
import scala.util.matching.Regex  
import scala.util.parsing.combinator._
import scala.swing._
import scala.swing.event.{Event => ScEvent, ActionEvent => ScActionEvent, _}
import scala.swing.Dialog

import javax.swing.JTable
import javax.swing.table.DefaultTableModel

import javax.swing.SwingUtilities
import javax.swing.UIManager
import javax.swing.ImageIcon
import javax.swing.event.HyperlinkEvent
import javax.swing.event.HyperlinkEvent.EventType
import javax.swing.event.HyperlinkListener
import java.awt.Desktop

case class ResourceNotFoundException(msg: String = "Unknown", cause: Throwable = null) extends Exception(msg, cause)

object Consts {
  val MAX_ROW = 10000
}

class ConfigLoader {
  private def getResourceStream(path: String): java.io.InputStream = {
    val file = new File(path)
    val resourcePath = path.replaceAll("""^\./""", "/")
    var url = getClass.getClassLoader.getResource(resourcePath)
    if (file.exists) {
      new FileInputStream(path)
    } else if (url != null) {
      url.openStream()
    } else {
      throw ResourceNotFoundException("リソース[" + path + "]が存在しません")
    }
  }

  protected var cache = MMap[String, Map[Any, Any]]()
  private def loadReal(path: String): Map[Any, Any] = loadReal(path, Map[Symbol, Any]())
  private def loadReal(path: String, opt: Map[Symbol, Any]): Map[Any, Any] = {
    var is: InputStream = null
    var reader: InputStreamReader = null
    try {
      is = getResourceStream(path)
      reader = new InputStreamReader(is)
      convertConfigMap((new Yaml).load(reader).asInstanceOf[java.util.HashMap[Any, Any]])
    } finally {
      reader.close()
      is.close()
    }
  }
  def clearCache() {
    cache.clear()
  }
  def load(path: String): Map[Any, Any] = {
    load(path, Map[Symbol, Any]())
  }
  def load(path: String, opt: Map[Symbol, Any]): Map[Any, Any] = {
    try {
      opt.get('cache) match {
        case Some(true) =>
          cache.get(path) match {
            case Some(config) => config
            case None =>
              val config = loadReal(path)
              cache += (path -> config)
              config
          }
        case _ => loadReal(path)
      }
    } catch {
      case ex: Exception => 
        Dialog.showMessage(title="ERROR", message="["+ path +"]のロードに失敗しました")
        throw ex
    }
  }
  def save(path: String, values: Map[Any, Any]) {
    var writer = new FileWriter(path)
    try {
      (new Yaml).dump(unconvertConfigMap(values), writer)
    } finally {
      writer.close()
    }
  }
  private def unconvertConfigMap(v: Map[Any, Any]): java.util.Map[Any, Any] = {
    import java.util.HashMap
    val map = new HashMap[Any, Any]
    v.foreach {
      case (k, value: Map[Any, Any]) => map += (k -> unconvertConfigMap(value))
      case (k, value: List[Any]) => map += (k -> unconvertConfigList(value))
      case (k, value) => map += (k -> value)
    }
    map
  }
  private def unconvertConfigList(v: List[Any]): java.util.List[Any] = {
    import java.util.ArrayList
    val list = new ArrayList[Any]
    v.foreach {
      case value: Map[Any, Any] => list += unconvertConfigMap(value)
      case value: List[Any] => list += unconvertConfigList(value)
      case value => list += value
    }
    list
  }
  private def convertConfigMap(v: java.util.Map[Any, Any]): Map[Any, Any] = {
    val map = v
    var immutableMap = Map[Any, Any]()
    map.foreach {
      case (k, vv: java.util.Map[Any, Any]) => immutableMap += (k -> convertConfigMap(vv))
      case (k, vv: java.util.List[Any]) => immutableMap += (k -> convertConfigList(vv))
      case (k, vv) => immutableMap += (k -> vv)
    }
    immutableMap
  }
  private def convertConfigList(v: java.util.List[Any]): List[Any] = {
    v.toList.map {
      case vv: java.util.Map[Any, Any] => convertConfigMap(vv)
      case vv: java.util.List[Any] => convertConfigList(vv)
      case vv => vv
    }
  }
}

class MGLayout extends java.awt.LayoutManager {
  import java.awt._

  def addLayoutComponent(name: String, comp: Component): Unit = {}
  def removeLayoutComponent(comp: Component): Unit = {}

  def preferredLayoutSize(target: Container): Dimension =
    target.getComponents.map(_.getPreferredSize).foldLeft(new Dimension) { (d, sz) =>
      d.width = math.max(d.width, sz.width)
      d.height += sz.height
      d
    }
  def minimumLayoutSize(target: Container): Dimension =
    target.getComponents.map(_.getMinimumSize).foldLeft(new Dimension) { (d, sz) =>
      d.width = math.max(d.width, sz.width)
      d.height += sz.height
      d
    }

  def layoutContainer(target: Container): Unit = {
    var y = 0
    target.getComponents.foreach { c =>
      val sz = c.getPreferredSize
      c.setBounds(0, y, sz.width, sz.height)
      y += sz.height
    }
  }
}

class ListPane extends Panel with SequentialContainer.Wrapper {
  override lazy val peer = new javax.swing.JPanel(new MGLayout) with SuperMixin
}

object ConfigLoader extends ConfigLoader

class HyperlinkHandler extends HyperlinkListener {
	def hyperlinkUpdate(e: HyperlinkEvent) {
		if (e.getEventType() == EventType.ACTIVATED) {
			val url = e.getURL()
			val dp = Desktop.getDesktop()
			try {
				dp.browse(url.toURI())
			} catch  {
        case ex: Exception => ex.printStackTrace()
			}
		}
	}
}

class MGTimeLine extends scala.swing.Table {
  val columnNames = Array[java.lang.Object](
      "アイコン", "ID", "ツイット", "M", "時間"
  )
  override protected def editor(row: Int, column: Int) = {
    null
  }

  override lazy val model = new MyTableModel(super.model.asInstanceOf[javax.swing.table.DefaultTableModel])
  columnNames.foreach((model.asInstanceOf[MyTableModel]).addColumn(_))
  peer.getColumnModel().getColumn(0).setPreferredWidth(60)
  peer.getColumnModel().getColumn(0).setMaxWidth(60)
  peer.getColumnModel().getColumn(0).setMinWidth(20)
  peer.getColumnModel().getColumn(1).setPreferredWidth(64)
  peer.getColumnModel().getColumn(1).setMaxWidth(80)
  peer.getColumnModel().getColumn(1).setMinWidth(50)
  peer.getColumnModel().getColumn(2).setPreferredWidth(400)
  peer.getColumnModel().getColumn(2).setMaxWidth(2048)
  peer.getColumnModel().getColumn(2).setMinWidth(128)
  peer.getColumnModel().getColumn(3).setPreferredWidth(20)
  peer.getColumnModel().getColumn(3).setMaxWidth(20)
  peer.getColumnModel().getColumn(3).setMinWidth(20)
  peer.getColumnModel().getColumn(4).setPreferredWidth(50)
  peer.getColumnModel().getColumn(4).setMaxWidth(128)
  peer.getColumnModel().getColumn(4).setMinWidth(50)
}
class MGTWindow {

  val frameWidth = 800
  val frameHeight = 600
  val rowHeight = 30

  lazy val timeLine = new MGTimeLine
  lazy val replyLine = new MGTimeLine
  lazy val dmLine = new MGTimeLine
  lazy val myText = new scala.swing.TextArea {
    yLayoutAlignment = 1.0
    charWrap = true
    lineWrap = true
    background = new Color(208, 208, 208)
    border = javax.swing.plaf.basic.BasicBorders.getTextFieldBorder
    maximumSize = new Dimension(9999, 60)
    yLayoutAlignment = java.awt.Component.BOTTOM_ALIGNMENT
  }

  lazy val selImg = new Label
  lazy val selMsg = new EditorPane("text/html", "") {
    import scala.swing.Alignment._
    editable = false
    minimumSize = new Dimension(100, 20)

    peer.putClientProperty(javax.swing.JEditorPane.HONOR_DISPLAY_PROPERTIES, java.lang.Boolean.TRUE)
    peer.setFont((new Label).font)
    peer.addHyperlinkListener(new HyperlinkHandler)

    override def text_= (txt: String) {
      super.text_=(txt.replaceAll("https?(:\\/\\/[-_.!~*\\'a-zA-Z0-9\\/?\\@&=+\\$%#]+)", "<a href='$0'>$0</a>"))
    }
  }

  lazy val top = new Frame {
    import FlowPanel.Alignment._
    
    override def closeOperation() {
      iconified()
    }

//    iconImage = ImageIO.read(classOf[Megane].getResourceAsStream("icon.png"))
    iconImage = ImageIO.read(classOf[Megane].getResourceAsStream("icon64.png"))

    size = new Dimension(frameWidth, frameHeight)
    title = "megane-twit"
    contents = new BoxPanel(Orientation.Vertical) {
      contents += new BoxPanel(Orientation.Vertical) {
        maximumSize = new Dimension(9999, 9999)

      contents += new TabbedPane {
        pages +=  new TabbedPane.Page("Home", new ScrollPane {
          yLayoutAlignment = 0.0
          contents = timeLine
        })
        pages += new TabbedPane.Page(" ＠ ", new ScrollPane {
          yLayoutAlignment = 0.0
          contents = replyLine
        })
        pages += new TabbedPane.Page(" DM ", new ScrollPane {
          yLayoutAlignment = 0.0
          contents = dmLine
        })
      }
      }
      contents += new BoxPanel(Orientation.Vertical) {
        maximumSize = new Dimension(9999, 100)
      contents += new BoxPanel(Orientation.Horizontal) {
        yLayoutAlignment = 1.0
        size = new Dimension(frameWidth, 30)
        maximumSize = new Dimension(9999, 80)
        contents += new FlowPanel(Left)(selImg)
        contents += selMsg
      }
      contents += myText
      contents += new BoxPanel(Orientation.Horizontal) {
        yLayoutAlignment = 1.0
        size = new Dimension(frameWidth, 26)
        contents += new FlowPanel(Left)(new BoxPanel(Orientation.Horizontal) {
          contents += new scala.swing.Button {
            name = "ダイレクトメッセージ"
            text = "DM"
            reactions += {
              case ButtonClicked(b) =>
                try {
                  val text = myText.text.trim
                  if (selectedTL != null && !text.isEmpty) {
                    if (text.length > 140) {
                      Dialog.showMessage(
                        title="文字数オーバー",
                        message=(text.length-140) + "字オーバーしています"
                      )
                    } else {
                      Dialog.showConfirmation(message="@" + selectedTL.screenName + "にDMを送りますか？") match {
                        case Dialog.Result.Yes =>
                          Megane.mgTwitter.sendDirectMessage(selectedTL.userID, text)
                          myText.text = ""
                        case _ => ()
                      }
                    }
                  }
                } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
                }
                ()
            }
          }
          contents += new scala.swing.Button {
            name = "引用"
            text = "QT"
            reactions += {
              case ButtonClicked(b) =>
                try {
                  if (selectedTL != null) {
                    if (tabIdx == 3) {
                      Dialog.showMessage(title="ERROR", message="ダイレクトメッセージは引用できません")
                    } else {
                      myText.text = "QT " + "@" + selectedTL.screenName + " " + selectedTL.msg
                    }
                  }
                } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
                }
                ()
            }
          }
          contents += new scala.swing.Button {
            name = "リツイート"
            text = "RT"
            reactions += {
              case ButtonClicked(b) =>
                try {
                  if (selectedTL != null) {
                    if (tabIdx == 1) {
                      Megane.mgTwitter.retweet(selectedTL.id)
                      updateTLInfo(selectedTL.id, selectedTL.copy(relation="RT"))
                    } else if (tabIdx == 2) {
                      Megane.mgTwitter.retweet(selectedTL.id)
                      updateReplyInfo(selectedTL.id, selectedTL.copy(relation="RT"))
                    } else if (tabIdx == 3) {
                      Dialog.showMessage(title="ERROR", message="ダイレクトメッセージはリツイートできません")
                    }
                  }
                } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
                }
                ()
            }
          }
          contents += new scala.swing.Button {
            name = "返信"
            text = "＠"
            reactions += {
              case ButtonClicked(b) =>
                try {
                  if (selectedTL != null) {
                    if (tabIdx == 3) {
                      Dialog.showMessage(title="ERROR", message="ダイレクトメッセージは返信できません")
                    } else {
                      myText.text = "@" + selectedTL.screenName + " " + myText.text 
                      replyStatus = selectedTL.id
                      replyScreenName = selectedTL.screenName
                    }
                  }
                } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
                }
                ()
            }
          }
          contents += new scala.swing.Button {
            name = "お気に入り"
            text = "☆"
            reactions += {
              case ButtonClicked(b) =>
                try {
                  if (selectedTL != null) {
                    if (tabIdx == 1) {
                      Megane.mgTwitter.fav(selectedTL.id)
                      updateTLInfo(selectedTL.id, selectedTL.copy(relation="☆"))
                    } else if (tabIdx == 2) {
                      Megane.mgTwitter.fav(selectedTL.id)
                      updateReplyInfo(selectedTL.id, selectedTL.copy(relation="☆"))
                    } else if (tabIdx == 3) {
                      Dialog.showMessage(title="ERROR", message="ダイレクトメッセージはお気に入り登録できません")
                    }
                  }
                } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
                }
                ()
            }
          }
        })
        contents += new FlowPanel(Right)(new scala.swing.Button {
          name = "Submit"
          text = "更新"
          reactions += {
            case ButtonClicked(b) =>
              try {
                val mgtw = Megane.mgTwitter
                val text = myText.text.trim
                if (!text.isEmpty) {
                  if (text.length > 140) {
                    Dialog.showMessage(
                      title="文字数オーバー",
                      message=(text.length-140) + "字オーバーしています"
                    )
                  } else {

                    if (replyScreenName != null && text.startsWith("@" + replyScreenName) && replyStatus > 0) {
                      val reply = new StatusUpdate(text)
                      reply.setInReplyToStatusId(replyStatus)
                      mgtw.twit(reply)
                    } else {
                      mgtw.twit(text)
                    }
                    myText.text = ""
                  }
                }
                Thread.sleep(1000)
                setTimeLine(mgtw.getHomeTimeLine())
              } catch {
                  case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
              }
              replyScreenName = null
              replyStatus = -1L
              ()
          }
        })
      }
      }

      listenTo(timeLine.selection)
      listenTo(dmLine.selection)
      listenTo(replyLine.selection)
      reactions += {
        case TableRowsSelected(source, range, adjusting) =>
          val selectedIdx = source.selection.rows.leadIndex
          if (source eq timeLine) {
            selectTwit(selectedIdx)
          } else if (source eq dmLine) {
            selectDM(selectedIdx)
          } else if (source eq replyLine) {
            selectReply(selectedIdx)
          }
      }
    }
    peer.setResizable(true)
    peer.setVisible(true)
    open()
  }

  var selectedTL: TimeLineInfo = null
  var replyScreenName: String = null
  var replyStatus: Long = -1L 
  var tabIdx: Long = 1

  def selectTwit(idx: Int) {
    selectedTL = TwitterInfo.timeline(idx)
    tabIdx = 1
    selImg.icon = selectedTL.image
    selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
    selImg.repaint()
  }
  def selectReply(idx: Int) {
    selectedTL = TwitterInfo.replyline(idx)
    tabIdx = 2
    selImg.icon = selectedTL.image
    selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
    selImg.repaint()
  }
  def selectDM(idx: Int) {
    selectedTL = TwitterInfo.dmline(idx)
    tabIdx = 3
    selImg.icon = selectedTL.image
    selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
    selImg.repaint()
  }


  def open() {
    Swing.onEDT {
      val t = top
			SwingUtilities.updateComponentTreeUI(t.peer)
      if (t.size == new Dimension(0,0)) t.pack()
      t.visible = true
    }
  }
  def setVisible(flag: Boolean) {
    val t = top
    t.visible = flag
  }
  def setTimeLine(timeline: List[TimeLineInfo]) {
    val addedLines = TwitterInfo.addTimeLine(timeline)
//    addedLines.foreach(println(_))
    for (tl <- addedLines) {
      this.timeLine.model.insertRow(0, timelineToRow(tl))
    }
    while (this.timeLine.model.getRowCount > Consts.MAX_ROW) {
      this.timeLine.model.removeRow(this.timeLine.model.getRowCount-1)
    }
    TwitterInfo.timeline = TwitterInfo.timeline.take(Consts.MAX_ROW)
  }
  def setReplyLine(timeline: List[TimeLineInfo]) {
    val addedLines = TwitterInfo.addReplyLine(timeline)
//    addedLines.foreach(println(_))
    for (tl <- addedLines) {
      this.replyLine.model.insertRow(0, timelineToRow(tl))
    }
    while (this.replyLine.model.getRowCount > Consts.MAX_ROW) {
      this.replyLine.model.removeRow(this.replyLine.model.getRowCount-1)
    }
    TwitterInfo.replyline = TwitterInfo.replyline.take(Consts.MAX_ROW)
  }
  def setDMLine(timeline: List[TimeLineInfo]) {
    val addedLines = TwitterInfo.addDMLine(timeline)
//    addedLines.foreach(println(_))
    for (tl <- addedLines) {
      this.dmLine.model.insertRow(0, timelineToRow(tl))
    }
    while (this.dmLine.model.getRowCount > Consts.MAX_ROW) {
      this.dmLine.model.removeRow(this.dmLine.model.getRowCount-1)
    }
    TwitterInfo.dmline = TwitterInfo.dmline.take(Consts.MAX_ROW)
  }

  def updateTLInfo(id: Long, tl: TimeLineInfo) {
    val idx = TwitterInfo.timeline.indexWhere(_.id == id)
    if (idx >= 0) {
      TwitterInfo.timeline = TwitterInfo.timeline.updated(idx, tl)
      this.timeLine.model.removeRow(idx)
      this.timeLine.model.insertRow(idx, timelineToRow(tl))
    }
  }
  def updateDMInfo(id: Long, tl: TimeLineInfo) {
    val idx = TwitterInfo.dmline.indexWhere(_.id == id)
    if (idx >= 0) {
      TwitterInfo.dmline = TwitterInfo.dmline.updated(idx, tl)
      this.dmLine.model.removeRow(idx)
      this.dmLine.model.insertRow(idx, timelineToRow(tl))
    }
  }
  def updateReplyInfo(id: Long, tl: TimeLineInfo) {
    val idx = TwitterInfo.replyline.indexWhere(_.id == id)
    if (idx >= 0) {
      TwitterInfo.replyline = TwitterInfo.replyline.updated(idx, tl)
      this.replyLine.model.removeRow(idx)
      this.replyLine.model.insertRow(idx, timelineToRow(tl))
    }
  }

  val formatter = new java.text.SimpleDateFormat("HH:mm:ss yy/MM/dd")
  def timelineToRow(tl: TimeLineInfo): Array[AnyRef] = {
    Array[AnyRef](tl.image, tl.screenName, tl.msg, tl.relation, formatter.format(tl.time))
  }
}

class ImageIconLoader {
  var cache = MMap[URL, ImageIcon]()

  def load(url: URL): ImageIcon = {
    cache.get(url) match {
      case Some(imageIcon) => imageIcon
      case None =>
        var imageIcon = new ImageIcon(url)
        cache += (url -> imageIcon)
        cache(url)
    }
  }

  def clearCache() {
    cache.clear()
  }
}

object ImageIconLoader extends ImageIconLoader

object TwitterInfo {
  var followIds = List[Long]()
  var followersIds = List[Long]()
  var lists = MMap[Int, List[Long]]()
  var userLists = MMap[Long, List[String]]()
  var user: User = null
  var timeline = List[TimeLineInfo]()
  var dmline = List[TimeLineInfo]()
  var replyline = List[TimeLineInfo]()
  def initialize(twitter: Twitter) {
    user = twitter.verifyCredentials()
    var ids1 = twitter.getFriendsIDs(-1)
    followIds :::= ids1.getIDs().toList
    var c1 = ids1.getNextCursor
    while (c1 != 0) {
      ids1 = twitter.getFriendsIDs(c1)
      followIds :::= ids1.getIDs().toList
      c1 = ids1.getNextCursor
    }
    var ids2 = twitter.getFollowersIDs(-1)
    followersIds :::= ids2.getIDs().toList
    var c2 = ids2.getNextCursor()
    while (c2 != 0) {
      ids2 = twitter.getFriendsIDs(c2)
      followersIds :::= ids2.getIDs().toList
      c2 = ids2.getNextCursor
    }
    def addUserList(userList: PagableResponseList[UserList]) {
      for (ul <- userList) {  
        if (lists.contains(ul.getId)) {
          lists += (ul.getId -> (ul.getUser.getId :: lists(ul.getId)))
        } else {
          lists += (ul.getId -> List(ul.getUser.getId))
        }
        if (userLists.contains(ul.getUser.getId)) {
          userLists += (ul.getUser.getId -> (ul.getName :: userLists(ul.getUser.getId)))
        } else {
          userLists += (ul.getUser.getId -> List(ul.getName))
        }
      }
    }
    var ulCursor = -1L
    var userList = twitter.getUserLists(user.getId, ulCursor)
    addUserList(userList)
    while (userList.hasNext) {
      ulCursor = userList.getNextCursor
      userList = twitter.getUserLists(user.getId, ulCursor)
      addUserList(userList)
    }

    // -- debug
//    println("follow: " + followIds.mkString(","))
//    println("follower: " + followersIds.mkString(","))
//    println("list: " + lists)
  }

  def addTimeLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
    var returns = newLines.filter(tl => !this.timeline.exists(v => v.id == tl.id))
    for (tl <- returns) {
      this.timeline = tl :: this.timeline
    }
    returns
  }
  def addDMLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
    var returns = newLines.filter(tl => !this.dmline.exists(v => v.id == tl.id))
    for (tl <- returns) {
      this.dmline = tl :: this.dmline
    }
    returns
  }
  def addReplyLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
    var returns = newLines.filter(tl => !this.replyline.exists(v => v.id == tl.id))
    for (tl <- returns) {
      this.replyline = tl :: this.replyline
    }
    returns
  }

  def displayRelation(status: Status): String = {
    val user = status.getUser
    val isFollow = followIds.contains(user.getId)
    val isFollowed = followersIds.contains(user.getId)
    if (status.isRetweet) {
      "RT"
    } else if (status.isFavorited) {
      "☆"
    } else if (user.isProtected) {
      "鍵"
    } else {
      if (isFollow && isFollowed) {
        "○"
      } else if (isFollow) {
        "●"
      } else if (isFollowed) {
        "△"
      } else {
        "―"
      }
    }
  }

  def displayListName(user: User): String = {
    if (userLists.contains(user.getId)) {
      userLists(user.getId).mkString("/")
    } else {
      "―"
    }
  }
}

class AuthDialog(_title: String, _message: String) extends Dialog {
  this.title = _title
  this.modal = true
  var result: Option[String] = None
  val pinField = new TextField

  contents = new BoxPanel(Orientation.Vertical) { 
    contents += new EditorPane("text/html", _message.replaceAll("https?(:\\/\\/[-_.!~*\\'a-zA-Z0-9\\/?\\@&=+\\$%#]+)", "<a href='$0'>$0</a>")) {
      editable = false
      peer.putClientProperty(javax.swing.JEditorPane.HONOR_DISPLAY_PROPERTIES, java.lang.Boolean.TRUE)
      peer.setFont((new Label).font)
      peer.addHyperlinkListener(new HyperlinkHandler)
    }
    contents += pinField
    contents += new BoxPanel(Orientation.Horizontal) { 
      contents += new scala.swing.Button {
        name = "Authorize"
        text = "認証"
        reactions += {
          case ButtonClicked(b) =>
            result = Some(pinField.text)
            close()
        }
      }
      contents += new scala.swing.Button {
        name = "Cancel"
        text = "キャンセル"
        reactions += {
          case ButtonClicked(b) =>
            result = None
            close()
        }
      }
    }
  }

  def getResult() = result
}

case class TimeLineInfo(id: Long, userID: Long, userName: String, screenName: String, msg: String, image: ImageIcon, time: Date, isRetweet: Boolean = false, isFavorited: Boolean = false, localtion: String = null, isProtected: Boolean = false, relation: String = null, listName: String = null, status: Status = null) {
  override def toString(): String = "@" + screenName + " - " + msg
}
class MGTwitter {
  private def statusToTimeLineInfo(status: Status): TimeLineInfo = {
    TimeLineInfo(
      id = status.getId,
      userID = status.getUser.getId,
      userName = status.getUser.getName,
      screenName = status.getUser.getScreenName,
      msg = status.getText,
      image = ImageIconLoader.load(status.getUser.getProfileImageUrlHttps),
      time = status.getCreatedAt,
      relation = TwitterInfo.displayRelation(status),
      listName = TwitterInfo.displayListName(status.getUser),
      isProtected = status.getUser.isProtected,
      status = status
    )
  }

  private def messageToTimeLineInfo(status: DirectMessage): TimeLineInfo = {
    TimeLineInfo(
      id = status.getId,
      userID = status.getSender.getId,
      userName = status.getSender.getName,
      screenName = status.getSender.getScreenName,
      msg = status.getText,
      image = ImageIconLoader.load(status.getSender.getProfileImageUrlHttps),
      time = status.getCreatedAt,
      relation = "○",
      listName = TwitterInfo.displayListName(status.getSender),
      isProtected = status.getSender.isProtected
    )
  }


  val twitter = login()
  TwitterInfo.initialize(twitter)

  def twit(text: String) {
    twitter.updateStatus(text)
  }

  def twit(status: StatusUpdate) {
    twitter.updateStatus(status)
  }

  def retweet(statusID: Long) {
    twitter.retweetStatus(statusID)
  }

  def fav(statusID: Long) {
    twitter.createFavorite(statusID)
  }

  def sendDirectMessage(userID: Long, text: String) {
    twitter.sendDirectMessage(userID, text)
  }
  def getReplyLine(): List[TimeLineInfo] = {
    var timelines = List[TimeLineInfo]()
    try {
        val statuses = twitter.getMentions(new Paging(1, 20))
        for (status <- statuses) {
          timelines ::= statusToTimeLineInfo(status)
        }
    } catch {
      case te: Exception =>
        te.printStackTrace()
        //Dialog.showMessage(title="ERROR", message="リプライの取得に失敗しました。: " + te.getMessage)
    }
    timelines
  }
  def getDMLine(): List[TimeLineInfo] = {
    var timelines = List[TimeLineInfo]()
    try {
        val statuses = twitter.getDirectMessages(new Paging(1, 20))
        for (status <- statuses) {
          timelines ::= messageToTimeLineInfo(status)
        }
    } catch {
      case te: Exception =>
        te.printStackTrace()
        //Dialog.showMessage(title="ERROR", message="DMの取得に失敗しました。: " + te.getMessage)
    }

    timelines
  }

  def getHomeTimeLine(): List[TimeLineInfo] = {
    var timelines = List[TimeLineInfo]()
    try {
        val statuses = twitter.getHomeTimeline(new Paging(1, 100))
        for (status <- statuses) {
          timelines ::= statusToTimeLineInfo(status)
        }
    } catch {
      case te: Exception =>
        te.printStackTrace()
        //Dialog.showMessage(title="ERROR", message="タイムラインの取得に失敗しました。: " + te.getMessage)
    }
    timelines
  }

  def login(): Twitter = {
    val tokenFile = new File("token")
    if (tokenFile.exists) {
      val config = ConfigLoader.load("token")
      val accessToken = new AccessToken(config("token").toString, config("tokenSecret").toString)
      new TwitterFactory().getInstance(accessToken)
    } else {
      val tw = new TwitterFactory().getInstance()
      // このファクトリインスタンスは再利用可能でスレッドセーフです
      val requestToken = tw.getOAuthRequestToken()
      var accessToken: AccessToken = null
      val br = new BufferedReader(new InputStreamReader(System.in))
      while (null == accessToken) {
//        val optPin = Dialog.showInput(
//          message="次のURLのページにアクセスし、PINコードを取得してください: \n"+requestToken.getAuthorizationURL(),
//          initial="")
        val dialog = new AuthDialog(
          "認証",
          "次のURLのページにアクセスし、PINコードを取得してください: <br>"+requestToken.getAuthorizationURL())
        dialog.open()
        val optPin = dialog.getResult
        optPin match {
          case Some(pin) => 
            try{
               if(pin.length() > 0){
                 accessToken = tw.getOAuthAccessToken(requestToken, pin)
               }else{
                 accessToken = tw.getOAuthAccessToken()
               }
            } catch {
              case te: TwitterException =>
                if(401 == te.getStatusCode()){
                  Dialog.showMessage(
                      title="ERROR",
                      message="認証に失敗しました。")
                }else{
                  te.printStackTrace()
                }
              case e: Exception => e.printStackTrace()
            }
          case _ =>
            Dialog.showMessage(
                title="終了します",
                message="認証なしでは利用できません。")
            System.exit(0)
        }
      }
      //将来の参照用に accessToken を永続化する

      ConfigLoader.save("token", Map("token" -> accessToken.getToken, "tokenSecret" -> accessToken.getTokenSecret))
      tw
    }
  }
}

object Megane {
  lazy val mgtw = new MGTwitter
  def mgTwitter: MGTwitter = mgtw
}

class Megane {
  var window: MGTWindow = new MGTWindow

  def execute(args: Array[String]) {
    val mgtw = Megane.mgTwitter
    // タスクトレイアイコン
    val image = ImageIO.read(classOf[Megane].getResourceAsStream("icon.png"))
    val icon = new TrayIcon(image)
    icon.addActionListener(new ActionListener {
      def actionPerformed(e: ActionEvent) {
        window.setTimeLine(mgtw.getHomeTimeLine())
        window.setDMLine(mgtw.getDMLine())
        window.setReplyLine(mgtw.getReplyLine())
        window.setVisible(true)
      }
    })
    window.open()
    window.setTimeLine(mgtw.getHomeTimeLine())
    window.setDMLine(mgtw.getDMLine())
    window.setReplyLine(mgtw.getReplyLine())
    window.setVisible(true)

    // ポップアップメニュー
    val menu = new PopupMenu
    // 終了メニュー
    val exitItem = new MenuItem("終了")
    exitItem.addActionListener(new ActionListener {
      def actionPerformed(e: ActionEvent) {
        System.exit(0)
      }
    })
    // メニューにメニューアイテムを追加
    menu.add(exitItem)
    icon.setPopupMenu(menu)

    // タスクトレイに格納
    SystemTray.getSystemTray().add(icon)

    var count = 0

    val task = new TimerTask {
        def run() {
          window.setTimeLine(mgtw.getHomeTimeLine())
          if (count % 10 == 0) {
            //返信・DM取得
            window.setDMLine(mgtw.getDMLine())
            window.setReplyLine(mgtw.getReplyLine())
          }
          count += 1
        }
    }
    val timer = new Timer
    timer.scheduleAtFixedRate(task, 0, 1 * 60 * 1000)
  }
}

//class MMLPlayer {
//  def play() {}
//}
//class MMLParser {
//  def parse(text: String) : MMLPlayer = {
//  }
//}
object Main {
  private var OS = 'Windows
  private var JVM_VERSION = "1.6"

  def checkOSAndJVMVersion(): Boolean = {
    var osname = System.getProperty("os.name")
    var jvmVersion = System.getProperty("java.version")
//    println("OS Name = " + osname)
    osname = osname.toLowerCase
//    println("Java Version = " + jvmVersion)
    var appendMessages = List("最新のJDKかJREをダウンロードして下さい。", "http://www.oracle.com/technetwork/java/javase/downloads")
    if (osname.startsWith("mac os x")) {
      OS = 'Mac
      appendMessages = List("Mac OS X 10.5 の Intel Core2Duo である場合は、", "「Java Preferences」の設定で Java SE 6 を有効にしてください")
    } else if (osname.startsWith("windows")) {
      OS = 'Windows
    } else if (osname.startsWith("linux")) {
      OS = 'Linux
    } else {
      println("unknown os! name = " + osname)
    }
    if (jvmVersion < "1.6") {
      println("Javaのバージョンが古いため、プログラムを実行できません。\n"
              +"java version = " + jvmVersion + ", (" +appendMessages.mkString(",")+")")
      return false
    }
    JVM_VERSION = jvmVersion
    true
  }

  val megane = new Megane

  def main(args: Array[String]) {
    checkOSAndJVMVersion
    if (OS == 'Windows) {
      UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
    }
    megane.execute(args)
  }
}
